<?php
defined('IN_MY_PHP') || die(0);

// 初始化本文件单独使用时涉及到的常量
defined('CRLF') || define('CRLF', "\r\n");
defined('LF') || define('LF', "\n");
defined('DS') || define('DS', DIRECTORY_SEPARATOR);
defined('UPLOAD_PATH') || define('UPLOAD_PATH',rtrim(str_ireplace('/', DS, $_SERVER['DOCUMENT_ROOT']), DS) . DS . 'uploads' .DS);

/**
 * 工具类（不含有具体的业务代码）.
 *
 * @author netmou <leiyanfo@sina.com>
 *
 */
class Utility
{

  /** 加载配置 */
  public function loadDic()
  {
    static $dicData = null;
    if($dicData == null){
      $file = PROJECT_PATH . 'data.dic.php';
      if (file_exists($file) && is_readable($file)) {
        $dicData = include $file;
      }else{
        trigger_error("配置文件:'{$file}' 未找到.", E_USER_ERROR);
      }
    }

    return $dicData;

  }
  /** 获取标签 */
  public function getDicLabel($name, $val, $hasStyle = false){

    $data = $this->loadDic();
    if(is_array($val)){
      $lastName = substr(strrchr($name, '.'), 1);
      $val = $val[$lastName];
    }
    foreach($data[$name] as $cVal => &$cLabel){
      if(is_scalar($cLabel) && $cVal == $val){
        return $cLabel;
      }
      if(is_array($cLabel) && $cLabel['val'] == $val){
        return $hasStyle?'<span class="'.$cLabel['style'].'">'.$cLabel['label'].'</span>':$cLabel['label'];
      }
    }
    return '';
    
  }
  /** 获取内容 */
  public function getDicData($name){
   
    $data = $this->loadDic();
    
    $retData = array();
    foreach($data[$name] as $cVal => &$cLabel){
      if(is_scalar($cLabel)){
        $retData[$cVal] = $cLabel;
      }
      if(is_array($cLabel)){
        $retData[$cLabel['val']] = $cLabel['label'];
      }
    }
    return $retData;
    
  }
  /**
   * 获取Excel文件单个Sheet内容
   */
  public function dataFromExcel($fileName, $rowStart = 1, $sheetIndex = 0)
  {
    set_time_limit(0);
    $result = array();
    try {
      $readType = PHPExcel_IOFactory::identify($fileName);  //在不知道文档类型的情况下获取
      $excelReader = PHPExcel_IOFactory::createReader($readType);
      $objPHPExcel = $excelReader->load($fileName);
      $objWorksheet = $objPHPExcel->getSheet($sheetIndex);
      $highestRow = $objWorksheet->getHighestRow();
      $highestColumn = $objWorksheet->getHighestColumn();
      $highestColumnIndex = PHPExcel_Cell::columnIndexFromString($highestColumn);
      for ($row = $rowStart; $row <= $highestRow; $row++) {
        for ($col = 0; $col <= $highestColumnIndex; $col++) {
          $cell = $objWorksheet->getCellByColumnAndRow($col, $row);
          if ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_NUMERIC) {
            $value = $cell->getFormattedValue();
          } elseif ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA) {
            $value = $cell->getCalculatedValue();
          }else{
            $value = $cell->getValue();
            if($value instanceof PHPExcel_RichText){
              $value = $value->getPlainText();
            }
          }
          $result[$row - $rowStart][$col] = trim($value);
        }
      }
    } catch (Exception $e) {
      trigger_error('File can\'nt be parsed by PHPExcel !', E_USER_ERROR);
    }
    
    return $result;
  }
  
  /**
  * 将二维数组的内容写入到excel文件并下载(文件格式仅限xls)
  */
  public function dataToExcel($data, $filename = 'data.xls'){
    set_time_limit(0);
    $phpexcel = new PHPExcel();
    $phpexcel->setActiveSheetIndex(0);
    $activeSheet = $phpexcel->getActiveSheet();
    $rowIndex = 1;
    foreach($data as $record){
      $colIndex = 0;
      foreach ($record as $val) {
        $colLetter = PHPExcel_Cell::stringFromColumnIndex($colIndex);
        $activeSheet->setCellValueExplicit($colLetter . $rowIndex, $val, PHPExcel_Cell_DataType::TYPE_STRING);
        ++$colIndex;
      }
      ++$rowIndex;
    }
    $filename = str_replace("+", "%20", urlencode($filename));
    
    header("Cache-Control: private, must-revalidate");
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment;filename={$filename}");
    header("Content-Transfer-Encoding: binary");
    $objWriter = PHPExcel_IOFactory::createWriter($phpexcel, 'Excel5');
    ob_end_clean();
    $objWriter->save("php://output");
    exit;
  }
  
  /**
  * 将数组里的内容进行编码转换
  */
  public function iconv($in_charset, $out_charset, $data){
    if(is_array($data)){
      foreach($data as $key=>&$val){
        $data[$key] = $this->iconv($in_charset, $out_charset, $val);
      }
      return $data;
    }else{
      return iconv($in_charset, $out_charset, $data);
    }
  }
  
  /**
   * 返回地球上两个经纬坐标之间的的距离，算法基于椭圆，返回值单位：米（M）.
   */
  public function getFlatternDistance($lat1, $lng1, $lat2, $lng2)
  {
    if ($lat1 == $lat2 && $lng1 == $lng2) {
      return 0;
    }
    $f = ($lat1 + $lat2) / 2 * pi() / 180.0;
    $g = ($lat1 - $lat2) / 2 * pi() / 180.0;
    $l = ($lng1 - $lng2) / 2 * pi() / 180.0;

    $sin_g = sin($g);
    $sin_l = sin($l);
    $sin_f = sin($f);

    $a = 6378137.0;
    $fl = 1 / 298.257;

    $sin_g_2 = $sin_g * $sin_g;
    $sin_l_2 = $sin_l * $sin_l;
    $sin_f_2 = $sin_f * $sin_f;

    $s = $sin_g_2 * (1 - $sin_l_2) + (1 - $sin_f_2) * $sin_l_2;
    $c = (1 - $sin_g_2) * (1 - $sin_l_2) + $sin_f_2 * $sin_l_2;

    $w = atan(sqrt($s / $c));
    $r = sqrt($s * $c) / $w;
    $d = 2 * $w * $a;
    $h1 = (3 * $r - 1) / 2 / $c;
    $h2 = (3 * $r + 1) / 2 / $s;

    return $d * (1 + $fl * ($h1 * $sin_f_2 * (1 - $sin_g_2) - $h2 * (1 - $sin_f_2) * $sin_g_2));
  }

  /**
   * 返回地球上两个经纬坐标之间的的方位角（PAB）.
   * 其中lat_a, lng_a是A的纬度和经度；lat_b, lng_b是B的纬度和经度，P为北极点
   * 参考：http://bbs.godeyes.cn/Upload/2006/12/06/205631.rar
   */
  public function getAzimuth($lat_a, $lng_a, $lat_b, $lng_b) {
    $d = 0;

    $lat_a = $lat_a * pi() / 180;
    $lng_a = $lng_a * pi() / 180;
    $lat_b = $lat_b * pi() / 180;
    $lng_b = $lng_b * pi() / 180;

    $d = sin($lat_a) * sin($lat_b) + cos($lat_a) * cos($lat_b) * cos($lng_b - $lng_a);
    $d = sqrt(1 - $d * $d);
    $d = cos($lat_b) * sin($lng_b - $lng_a) / $d;
    $d = asin($d) * 180 / pi();

    return $d;
  }

  /**
   * 判断点是否在多边形内,算法基于多边形外的点与多边形的边相交，有偶数个交点.
   */
  public function pointInPolygon($p, $points)
  {
    $cross = 0;
    $size = count($points);
    for ($i = 0; $i < $size; ++$i) {
      $p1 = $points[$i];
      $p2 = $points[($i + 1) % $size];
      if ($p1['lat'] == $p2['lat']) {
        continue;
      }
      if ($p['lat'] < min($p1['lat'], $p2['lat'])) {
        continue;
      }
      if ($p['lat'] >= max($p1['lat'], $p2['lat'])) {
        continue;
      }
      $x = ($p['lat'] - $p1['lat']) * ($p2['lng'] - $p1['lng']) / ($p2['lat'] - $p1['lat']) + $p1['lng'];
      if ($x > $p['lng']) {
        ++$cross;
      }
    }

    return $cross % 2 == 1;
  }

  public function getFileLines($file, $strip_crlf = true){
    $lines = array();
    if (file_exists($file)){
      $handle = fopen($file, "r");
      while (!feof($handle)) {
        $line = fgets($handle);
        if($strip_crlf){
          $line = rtrim($line, CRLF);
        }
        $lines[] = $line;
      }
      fclose($handle);
      return $lines;
    }
    trigger_error("文件{$file}不存在！",E_USER_ERROR);
  }
  
  /**根据身份证获取年龄*/
  public function fetchAgeByIdCard($id_card)
  {
    if (!preg_match('/^\\d{17}[\\dx]$/i', $id_card)) {
      return 0;
    }
    $birth = strtotime(substr($id_card, 6, 8));

    return ceil((time() - $birth) / 86400 / 365);
  }

  /**
   * 去除XSS（跨站脚本攻击）的函数
   */
  public function removeXSS($val)
  {
    if(!is_scalar($val)){
      trigger_error("Val is not a scalar!", E_USER_ERROR);
    }
    // #x9 | #xA | #xD | [#x20-#xD7FF] ...  compatibility characters
    // 过滤掉非法字符(删除ASCII中部分不可见字符，回车、换行及水平制表等字符被保留)
    $val = preg_replace('/([\x00-\x08\x0b-\x0c\x0e-\x19])/', '', $val);
    
    // 将内容中的16进和十进制html编码的ASCII字符进行还原
    $search = 'abcdefghijklmnopqrstuvwxyz';
    $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $search .= '1234567890!@#$%^&*()';
    $search .= '~`";:?+/={}[]-_|\'\\';
    for ($i = 0; $i < strlen($search); ++$i) {
      $val = preg_replace('/(&#x0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val);
      $val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val);
    }
    
    // 内容中含有如下可能引入外部文件的标签应过滤掉
    $externSet = array('script', 'link', 'style', 'embed', 'object', 'iframe', 'frame', 'xml');
    foreach($externSet as $extern){
      $val = preg_replace('/<'.$extern.'.*?>/is', '<xss>', $val);
      $val = preg_replace('/<\\/'.$extern.'>/is', '</xss>', $val);
    }
    
    // 内容中如含有JS事件属性的标签应过滤掉
    $val = preg_replace('/<\\w+.+?on\\w+\\s*=.+?>/is', '<xss>', $val);
    // 链接中如含有script代码应过滤掉
    $val = preg_replace('/<\\w+.+?script:.+?>/is', '<xss>', $val);
    
    return $val;
  }

  /**
   * 本函数针对外部输入数据，递归过滤XSS攻击，SQL转义，标量检测
   * 本函数应用在数据保存时，主要防止sql注入、XSS攻击.
   * @param $input      array 被检查内容，null或没有传参时默认为$_GET, $_POST
   * @param $opt        array 检查项：xss_remove, slash_add
   */
  public function inputData($input = null, $opt = array())
  {
    if(func_num_args() == 0){
      $input = array_merge($_GET, $_POST);
    }
    $opt_init = array(
      'xss_remove'=>true,
      'slash_add'=>false,
    );
    $opt_final = $opt_init + $opt;

    if(is_scalar($input)){
      if ($opt_final['slash_add']) {
        $input = $this->addSlash($input);
      }
      if ($opt_final['xss_remove']) {
        $input = $this->removeXSS($input);
      }
      return $input;
    }
    $recRst = array();
    foreach($input as $key=>$val){
      $key = $this->{__FUNCTION__}($key, $opt);
      $recRst[$key] = $this->{__FUNCTION__}($val, $opt);
    }
    return $recRst;
  }
  /**
   * 本函数不同于XSS攻击过滤，本函数仅作HTML转义处理，同样能防止过滤XSS攻击
   * 本函数应用在数据输出时，主要防止数据影响页面代码.
   */
  public function outputData($input=null, $stripSlash = false)
  {
    if(func_num_args() == 0){
      $input = $_REQUEST;
    }

    if (is_array($input)) {
      $recRst = array();
      foreach ($input as $key => $val) {
        $key = $this->{__FUNCTION__}($key, $stripSlash);
        $recRst[$key] = $this->{__FUNCTION__}($val, $stripSlash);
      }
      return $recRst;
    }

    if ($stripSlash) {
      $input = stripslashes($input);
    }
    $f = array('&','\'','"','<','>');
    $t = array('&amp;','&#039;','&quot;','&lt;','&gt;');

    return str_replace($f, $t, $input);
  }

  /**
   * 过滤字符串中的空白字符（33个ascii字符，不会对utf8造成破坏）.
   */
  public function clearHideChar($str, $except=array(9, 10, 13)){
    $retStr = '';
    for($i = 0; $i < strlen($str); $i++){
      $ascii = ord($str[$i]);
      // 33个不可见字符(0~31,127),默认保留回车(CR:13)、换行（LF:10）、水平制表（TAB:9）等字符
      if(($ascii < 32 || $ascii == 127) && !in_array($ascii, $except)){
        continue;
      }
      $retStr .= sprintf("%02x", $ascii);
    }
    return pack("H*", $retStr);
  }
  

  public function contain($file, $path = null, $importVars = array())
  {
    $file = str_replace('/', DS, $file);
    if ($path != null) {
      $file = rtrim(str_replace('/', DS, $path), DS) . DS . $file;
    }

    if (file_exists($file) && is_readable($file)) {
      //禁止访问不在网站根目录下的内容
      if (stripos(realpath($file), PROJECT_PATH) !== 0) {
        trigger_error("file:'{$file}' access denied.", E_USER_ERROR);
      }
      // 导入对引入文件可见的变量
      extract($importVars);

      return include $file; // the method must be include
    }
    trigger_error("file:'{$file}' is not found.", E_USER_ERROR);
  }

  /**
   * 本函数检查指定的URL是否可访问，需要PHP开启CURL.
   */
  public function curlAvailable($url)
  {
    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_TIMEOUT, 5); // 超时时间
    curl_setopt($curl, CURLOPT_HEADER, 1); // 返回header
    curl_setopt($curl, CURLOPT_NOBODY, true); // 不取回数据
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 关闭ssl验证
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); // 关闭ssl验证
    $rst = curl_exec($curl); // 发送请求
    if ($rst !== false && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 200) {
      return true;
    }
    curl_close($curl);

    return false;
  }


  /**
   * 使用curl模拟 http post(不支持附件)
   *
   * @param [type] $url
   * @param array $post 键值对数组
   * @param array $options 
   * @return void
   * @todo 未测试
   */
  public function curlPost($url, array $post = NULL, array $options = array()){
    $defaults = array(
      CURLOPT_POST => 1,
      CURLOPT_HEADER => 0, // 是否返回的http响应头 
      CURLOPT_URL => $url, // 设置访问的url地址
      CURLOPT_FRESH_CONNECT => 1, // 强制获取一个新的连接，替代缓存中的连接。
      CURLOPT_RETURNTRANSFER => 1, // 文件流的形式返回
      CURLOPT_FORBID_REUSE => 1, // 在完成交互以后强迫断开连接，不能重用
      CURLOPT_TIMEOUT => 8, // 设置超时 
      CURLOPT_POSTFIELDS => http_build_query($post),
      CURLOPT_SSL_VERIFYPEER=>0, // 关闭ssl验证
      CURLOPT_SSL_VERIFYHOST=>0,
    );

    $ch = curl_init();
    curl_setopt_array($ch, $defaults + $options);
    $result = curl_exec($ch);
    // echo curl_error($ch);
    curl_close($ch);
    return $result;
  }

  /**
   * 使用curl模拟 http get
   *
   * @param [type] $url
   * @param array $options 
   * @return void
   */
  public function curlGet($url, $options = array()){
    $defaults = array(
      CURLOPT_URL => $url, // 设置访问的url地址 
      CURLOPT_HEADER => 0, // 是否返回的http响应头 
      CURLOPT_TIMEOUT => 5, // 设置超时
      CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'], // 用户访问代理 User-Agent 
      CURLOPT_REFERER => $_SERVER['HTTP_REFERER'], // 设置 referer 
      CURLOPT_FOLLOWLOCATION => 1, // 跟踪301 
      CURLOPT_RETURNTRANSFER => 1, // 文件流的形式返回
      CURLOPT_SSL_VERIFYPEER=>0, // 关闭ssl验证
      CURLOPT_SSL_VERIFYHOST=>0,
    );
    $ch = curl_init();
    curl_setopt_array($ch, $defaults + $options);
    $result = curl_exec($ch);
    // echo curl_error($ch);
    curl_close($ch);
    return $result;
  }


  /**
   * 临时重置页面，建议应用此函数时开启输出缓冲.
   */
  public function redirect($url)
  {
    if (!$this->headerSent(true)) {
      header('HTTP/1.1 302 Temporarily Moved');
      header('location: '.$url);
      exit(0);
    }
    exit("页面跳转到‘{$url}’失败！");
  }

  /** 构造URL参数，$params */
  function buildParams($params, $extra = array(), $except = array()){
    if(is_string($params)){
      $pos = strpos($params,'?');
      $params = ($pos===false)? $params : substr($params,$pos+1);
      parse_str($params, $parseRst);
      $params = $parseRst;
    }
    if(!empty($extra)){
      $params = array_merge($extra, $params);
    }
    if(is_string($except)){
      $except = array($except);
    }
    foreach($except as $exp){
      unset($params[$exp]);
    }
    $uri = '?'.http_build_query($params);
    // 特殊html实体字符进行转码
    $f = array('&','\'','"','<','>');
    $t = array('&amp;','&#039;','&quot;','&lt;','&gt;');
    return str_replace($f, $t, $uri);
  }

  /**
   * 给出js-Alert提示并跳转页面.
   */
  public function jsAlert($msg, $go = '', $charset='utf-8')
  {
    if(!$this->headerSent()){
      while(ob_get_level() > 0){
        ob_end_clean();
      }
      header("Content-type:text/html;charset={$charset}");
      header("Cache-control: private");
    }
    echo "<script type=\"text/javascript\">";
    $msg = $this->toJsVar($msg, '"', false);
    echo "alert({$msg});";
    if (is_numeric($go)) {
      echo "window.history.go({$go});";
    } elseif (is_string($go) && $go !== '') {
      echo 'window.location="'.$go."\";";
    } elseif(is_array($go)){
      echo "window.location=\"?".http_build_query($go)."\";";
    } else {
      if($_SERVER['REQUEST_METHOD']=='POST'){
        $query = ($_GET == null)? '?' : '?' . http_build_query($_GET);
        $query = $this->toJsVar($query, '"', false);
        echo "window.location={$query};";
      }else{
        echo "window.history.go(-1);";
      }
      
    }
    echo '</script>';
    exit;
  }
  /**
   * 给出js-Close提示并关闭页面.
   */
  public function jsClose($msg = '', $charset='utf-8')
  {
    if(!$this->headerSent()){
      while(ob_get_level() > 0){
        ob_end_clean();
      }
      header("Content-type:text/html;charset={$charset}");
    }
    echo "<script type=\"text/javascript\">";
    if($msg != ''){
      $msg = $this->toJsVar($msg, '"', false);
      echo "alert({$msg});";
    }
    echo "window.close();";
    echo '</script>';
    exit;
  }
  
  /**
   * 终止执行.
   */
  public function quit($msg = null, $backURL = '', $charset='UTF-8')
  {
    if(!$this->headerSent()){
      while(ob_get_level() > 0){
        ob_end_clean();
      }
      header("Content-type:text/html;charset={$charset}");
    }
    if($msg != null){
      if($backURL == -1){
        echo "<span style=\"color:#00c;text-decoration:underline;cursor:pointer;\" onclick=\"history.go({$backURL})\">$msg</span>";
      }else if(is_string($backURL) && $backURL != ''){
        $backURL = htmlspecialchars($backURL);
        echo "<a style=\"color:#00c;text-decoration:underline;cursor:pointer;\" href=\"{$backURL}\">$msg</a>";
      }else{
        echo "<span style=\"color:#c00;\">$msg</span>";;
      }
    }
    exit;
  }

  public function endWith($str, $suffixs = array()){
    if(!is_array($suffixs)){
      $suffixs = array($suffixs);
    }
    $len = strlen($str);
    foreach($suffixs as $suffix){
      $sl = strlen($suffix);
      if($len >= $sl && substr($str,0-$sl) == $suffix){
        return true;
      }
    }
    return false;
  }
  
  public function startWith($str, $prefixs = array()){
    if(!is_array($prefixs)){
      $prefixs = array($prefixs);
    }
    $len = strlen($str);
    foreach($prefixs as $prefix){
      $pl = strlen($prefix);
      if($len >= $pl && substr($str, 0, $pl) == $prefix){
        return true;
      }
    }
    return false;
  }

  public function forceDownload($filename, &$data, $mime='application/octet-stream'){
    if(!$this->headerSent(true)){
      while(ob_get_level() > 0){
        ob_end_clean();
      }
      $filesize = strlen($data);
      $filename = str_replace("+", "%20", urlencode($filename));
      header('Content-Type: '.$mime);
      header('Cache-Control: private, no-cache');
      header('Content-Disposition: attachment; filename="'.$filename.'"');
      header('Content-Transfer-Encoding: binary');
      header('Content-Length: '.$filesize);
      echo $data;
      exit;
    }

  }

  public function headerSent($trigger_error = false){
    //$filename, $linenum 引用模式，不用先赋值
    if (headers_sent($filename, $linenum)) {
      if($trigger_error == true){
        trigger_error("Header content has been sent in {$filename} on line {$linenum}!", E_USER_ERROR);
      }
      return true;
    }
    return false;
  }


  /**
   * 设置浏览器缓存当前页面的时效.
   */
  public function cache($seconds = 3600)
  {
    if(!$this->headerSent(true)){
      header('Last-Modified: '.gmdate('D, d M Y H:i:s', time()).' GMT');
      header('Cache-Control: max-age='.$seconds);
      header('Cache-Control: private, must-revalidate');
    }
  }

  /**
   * 禁止浏览器缓存当前页面.
   */
  public function noCache()
  {
    if(!$this->headerSent(true)){
      header('Cache-Control: no-store');
    }
  }

  /**
   * 将PHP变量的值嵌入在js代码中，转义引号，换行等，使其成为合法的js字面量.
   */
  public function toJsVar($val, $strQuote = '"', $is_html_env = true)
  {
    if (is_numeric($val)) {
      return $val;
    } elseif (is_string($val)) {
      $val = str_replace('\\', '\\\\', $val); //转移自身，同时确保‘&,\b,\f’不被错误的转义
      $val = str_replace('"', '\\"', $val); //双引号
      $val = str_replace("'", '\\\'', $val); //单引号
      $val = str_replace("\t", '\\t', $val); //水平制表
      $val = str_replace("\n", '\\n', $val); //换行
      $val = str_replace("\r", '\\r', $val); //回车
      if(true == $is_html_env){
        return htmlspecialchars($strQuote.$val.$strQuote);
      }
      return $strQuote.$val.$strQuote;
    } elseif (is_bool($val)) {
      return $val == true ? 'true' : 'false';
    } else if (is_array($val)){
      return json_encode($val);
    }

    return 'null';
  }

  /**
   * 用正则表达式判断密码强度
   * 返回值0：密码长度不足或太长，其他代表含有大小写、数字、及特殊字符等这四类的个数.
   */
  public function checkSecurity($pwd, $min = 6, $max = 20)
  {
    $match = array();
    preg_match('/^(?:([a-z])|([A-Z])|([0-9])|([\\W_])){'.$min.','.$max.'}|(.*?)$/', $pwd, $match);
    // 所有子组数加上整个匹配结果自身共6个，和最小密码长度无关
    // 当长度不足时前面的子组不匹配，前面都为空，最后一个匹配共6个
    // 当长度满足时最后一个子组不匹配，子组数加整个匹配结果共5个（子组部分可能为空）
    if (count($match) == 6) {
      return 0;
    } else {
      $counter = 0; // 前面子组不为空即为有这一类字符
      for ($i = 1;$i < count($match);++$i) {
        if ($match[$i] !== '') {
          ++$counter;
        }
      }

      return $counter;
    }
  }

/**
 * 二维数组的多维组合，类似笛卡尔乘积
 * 对下面三组abc任意值的不重复组合
 * $data = array(
 *   'A' => [0=>'红色',1=>'黄色'], //range1
 *   'B' => [0=>'M',1=>'L',2=>'XL'], //range2
 *   'C' => [0=>'轻',1=>'重'], //range3
 * );
 * 返回数据格式：
 * $ret = array(
 *   '0_0_0' => array('A'=>a1,'B'=>b1,'C'=>c1),
 *   '0_0_1' => array('A'=>a1,'B'=>b1,'C'=>c2),
 *   '0_1_0' => array('A'=>a1,'B'=>b2,'C'=>c1),
 *   '0_1_1' => array('A'=>a1,'B'=>b2,'C'=>cc),
 *   '1_0_0' => array('A'=>a2,'B'=>b1,'C'=>c1),
 *   '1_0_1' => array('A'=>a2,'B'=>b1,'C'=>c2),
 *   '1_1_0' => array('A'=>a2,'B'=>b2,'C'=>c1),
 *   '1_1_1' => array('A'=>a2,'B'=>b2,'C'=>c2)
 * );
 */
  function arrayDikaer($data){
    $lastRst = array();
    foreach($data as $rng=>$range){
      if(empty($range)){
        continue;
      }
      if(empty($lastRst)){
        foreach($range as $index => $val){
          $lastRst[$index] = array($rng => $val);
        }
      }else{
        $newRst = array();
        foreach($lastRst as $rst=>$result){
          foreach($range as $index => $val){
            $result[$rng] = $val;
            $newRst[$rst.'_'.$index] = $result; 
          }
        }
        $lastRst = $newRst;
      }
    }
    return $lastRst;
  }
  
  /**
   * 修正explode返回结果.
   */
  public function explode($sp, $str)
  {
    if ($str === '' || $str === null) {
      return array();
    }
    if(is_array($str)){
      return $str;
    }

    return explode($sp, $str);
  }

  /**
   * 修正implode，允许空值.
   */
  public function implode($sp, $arr)
  {
    if ($arr === '' || $arr === null) {
      return '';
    }
    if(is_scalar($arr)){
      return $arr;
    }

    return implode($sp, $arr);
  }
  
  /**修正默认值1970问题*/
  public function date($pattern, $unixtime = null){
    if(func_num_args() == 1){
      return date($pattern);
    }
    if($unixtime === null || !is_numeric($unixtime)){
      return $unixtime;
    }
    return date($pattern, $unixtime);
  }

  /** 返回字符型数字以修正 ip2long 因溢出返回负数的问题(32位机器) */
  public function ipTolong($ip){
    return sprintf("%u", ip2long($ip));
  }

  /**
   * 针对外部输入，将变量中特殊字符转义.
   */
  public function addSlash($data, $force = false)
  {
    // PHP 5.4 起 magic_quotes_gpc 始终返回 FALSE
    // 如果已经进行魔术引号转义就直接返回数据（非强制模式）
    if (get_magic_quotes_gpc() && $force == false) {
      return $data;
    }

    if (is_array($data)) {
      $recRst = array();
      foreach ($data as $key => $val) {
        $key = $this->{__FUNCTION__}($key, $force);
        $recRst[$key] = $this->{__FUNCTION__}($val, $force);
      }

      return $recRst;
    }

    return addslashes($data);
  }
  

  /**
   * 针对外部输入，将变量中经过转义的特殊字符反转义.
   */
  public function stripSlash($data, $force = false)
  {
    // PHP 5.4 起 magic_quotes_gpc 始终返回 FALSE
    if ($force == true || get_magic_quotes_gpc()) {
      if (is_array($data)) {
        $recRst = array();
        foreach ($data as $key => $val) {
          $key = $this->{__FUNCTION__}($key, $force);
          $recRst[$key] = $this->{__FUNCTION__}($val, $force);
        }

        return $recRst;
      }

      return stripslashes($data);
    }

    return $data;
  }

  /**
   * 移除全局变量的影响 only for PHP 5.2.
   */
  public function clearGlobal()
  {
    if (!ini_get('register_globals')) {
      return;
    }
    if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
      trigger_error('GLOBALS overwrite attempt detected!!', E_USER_ERROR);
    }
    $except = array('GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
    $_SESSION = isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array();
    $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES, $_SESSION);
    foreach ($input as $k => $v) {
      if (!in_array($k, $except) && isset($GLOBALS[$k])) {
        unset($GLOBALS[$k]);
      }
    }
  }
  
  /**
  * 修复在进行json化的时候，因键值关联数组没有数据而导致解析为js数组的问题
  * 如：用户有数据应json化为 {name:xxx, age:xxx}，因没有数据而json化为 []
  * 解决方式：将空数组转换成 null
  */
  function json_encode_fixed($data){
    $emptyKeySet = array();
    $stack = array(array('keys'=>array(), 'val'=>&$data));
    while(count($stack) != 0){
      $lastStack = array();
      foreach($stack as &$elem){
        if(is_array($elem['val']) && !empty($elem['val'])){
          foreach($elem['val'] as $subkey =>&$subelem){
            $multiKeySet = $elem['keys'];
            array_push($multiKeySet, $subkey);
            $lastStack[] = array('keys'=>$multiKeySet, 'val'=>$subelem);
          }
        }else if($elem['val'] === array()){
          $emptyKeySet[] = $elem['keys'];
        }
      }
      $stack = $lastStack;
    }
    foreach($emptyKeySet as $keySet){
      $val = &$data;
      foreach($keySet as $key){
        $val = &$val[$key];
      }
      $val = null;
    }
    return $data;
  }

  /**
   * 在utf-8的字符编码的字符串中截取部分，本函数适用于全部字符.
   */
  public function subUtf8($str, $len, $pad = null)
  {
    $offset = 0;
    $chars = 0;
    $rst = null;
    $flag = array(0x3F, 0x1F, 0xF, 0x7, 0x3, 0x0);
    while ($chars < $len && $offset < strlen($str)) {
      $high = ord(substr($str, $offset, 1));
      // 开始字节最高两位不能为2,且不能为ff和fe
      if($high >> 6 == 2 || $high >> 1 == 0x7F){
        $offset++;
      }
      for ($i = 2; $i < 8; ++$i) {
        if ($high >> $i == $flag[$i - 2]) {
          $rst .= substr($str, $offset, 8 - $i);
          $offset = $offset + 8 - $i;
          ++$chars;
          break;
        }
      }
    }

    return $rst == $str ? $rst : $rst.$pad;
  }
  
  /**
   * 将utf8编码的数据打散为单个字符集合
   */
  public function getUtf8Chars($str)
  {
    $offset = 0;
    $ret = array();
    $flag = array(0x3F, 0x1F, 0xF, 0x7, 0x3, 0x0);
    while ($offset < strlen($str)) {
      $high = ord(substr($str, $offset, 1));
      // 开始字节最高两位不能为2,且不能为ff和fe
      if($high >> 6 == 2 || $high >> 1 == 0x7F){
        $offset++;
      }
      for ($i = 2; $i < 8; ++$i) {
        if ($high >> $i == $flag[$i - 2]) {
          $ret[] = substr($str, $offset, 8 - $i);
          $offset = $offset + 8 - $i;
          break;
        }
      }
    }

    return $ret;
  }
  


  public function ltrim($string, $chars = "\0x20\0x09\0x0A\0x0D\0x00\0x0B"){
    $strChars = $this->getUtf8Chars($string);
    $remChars = $this->getUtf8Chars($chars);
    for($i=0; $i<count($strChars); $i++){
      if(in_array($strChars[$i], $remChars)){
        $strChars[$i] = '';
      }else{
        break;
      }
    }
    return implode('',$strChars);

  }
  public function rtrim($string, $chars = "\0x20\0x09\0x0A\0x0D\0x00\0x0B"){
    $strChars = $this->getUtf8Chars($string);
    $remChars = $this->getUtf8Chars($chars);
    for($i=count($strChars)-1; $i>=0; $i--){
      if(in_array($strChars[$i], $remChars)){
        $strChars[$i] = '';
      }else{
        break;
      }
    }
    return implode('',$strChars);

  }
  public function trim($string, $chars = "\0x20\0x09\0x0A\0x0D\0x00\0x0B"){
    $strChars = $this->getUtf8Chars($string);
    $remChars = $this->getUtf8Chars($chars);
    $length = count($strChars);
    for($i=0; $i<$length; $i++){
      if(in_array($strChars[$i], $remChars)){
        $strChars[$i] = '';
      }else{
        break;
      }
    }
    for($i = $length-1; $i>=0; $i--){
      if(in_array($strChars[$i], $remChars)){
        $strChars[$i] = '';
      }else{
        break;
      }
    }
    return implode('',$strChars);

  }
  /**
   * 返回一个精确到微秒数时间戳（适用于2002-2286年）
   */
  public function getMicroTime(){
    //末尾补零凑齐14位 eg:1516082380
    return substr(str_replace('.', '', microtime(true)).'00000',0,14);
  }

  /**
   * 将Excel日期转换为unixtime
   * Excel日期是从1900到现在的天数
   * unixtime是从1970到现在的秒数
   */
  public function timeFromExcel($val, $zone = 8)
  {
    return intval(($val-25569)*86400-$zone*3600);
  }

  /**
   * 将unixtime转换为Excel日期
   * Excel日期是从1900到现在的天数
   * unixtime是从1970到现在的秒数
   */
  public function timetoExcel($unix, $zone = 8)
  {
    return ($unix+$zone*3600)/86400+25569;
  }

  /**
   * 隐藏字符串的部分，如电话，身份证等，适用于ASCII字符组成的字符串.
   */
  public function textPartHide($text, $dchar = '*', $index = 0.48)
  {
    $text_len = strlen($text);
    $hide_len = (int)ceil($text_len *  $index); 
    $start = ceil($text_len -  $hide_len)/2;
    
    $replace = str_repeat($dchar, $hide_len);
    //echo gettype($start);
    return substr_replace($text, $replace, $start, $hide_len);
  }

  /**
   * 获取客户端浏览器的IP地址.
   * 如果一个 HTTP 请求到达服务器之前，经过了三个代理 Proxy1、Proxy2、Proxy3，
   * 其IP 分别为 IP1、IP2、IP3，其中Proxy3直连服务器，用户真实 IP 为 IP0，
   * 那么按照 XFF 标准，服务端最终会收到以下信息：
   * HTTP_X_FORWARDED_FOR: IP0, IP1, IP2 // 可以被伪造
   * REMOTE_ADDR：IP3 // 无法伪造，但线上可能是代理服务器IP
   * 本函数返回 IP0, IP1, IP2|IP3 形式记录的信息（最大长度65）
   */
  public function getIPAddr(){

    if($_SERVER['X_FORWARDED_FOR'] != ''){

      return $_SERVER['X_FORWARDED_FOR'] . '|' . $_SERVER['REMOTE_ADDR'];
    }

    if($_SERVER['HTTP_X_FORWARDED_FOR'] != ''){

      return $_SERVER['HTTP_X_FORWARDED_FOR'] . '|' . $_SERVER['REMOTE_ADDR'];
    }

    return $_SERVER['REMOTE_ADDR'];
  }

  /**
   * 生成指定长度的随机字符串（由常用ASCII字符构成），通常用作干扰盐值.
   */
  public function randStr($length, $nacs='na')
  {
    static $nacsSet=array(
      'n' => '0123456789',
      'a' =>  'abcdefghijklmnopqrstuvwxyz',
      'c' =>  'ABCDEFGHIGKLMNOPQRSTUVWXYZ',
      's' => '_,.?!@#^$()%+*/='
    );
    $charSet='';
    $ret = '';
    for($i = 0; $i<strlen($nacs); $i++) {
      $index = $nacs[$i];
      $charSet .= $nacsSet[$index];
    }
    for ($i = 0; $i < $length; ++$i) {
      $index = mt_rand(0, strlen($charSet)-1);
      $ret .= $charSet[$index];
    }

    return $ret;
  }

  /**
   * 随机生成gb2312中的一个一级汉字（16-55区，每区94个汉字，即常用汉字）
   * 其中第55区有5个空位是：D7FA-D7FE，需要跳过
   */
  public function randCNChar($charset = 'UTF-8'){
    $high = rand(0xB0, 0xD7);
    $low = ($high == 0xD7)? rand(0xA1, 0xF9) : rand(0xA1, 0xFE);
    $dbChar = chr($high) . chr($low);
    return iconv('GB2312', $charset, $dbChar);
  }

  /**
   * 返回身份证号前17位的校验值
   */
  private function getCnIDCheckCode($idCard)
  {
    $W = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1);
    $A = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
    $s = 0;
    for ($i = 0; $i < 17; ++$i) {
      $s = $s + substr($idCard, $i, 1) * $W [$i];
    }

    return $A [$s % 11];
  }

  /**
   * 将15位身份证号转换为18位.
   */
  public function formatCnID($idCard)
  {
    if (15 != strlen($idCard)) {
      return $idCard;
    }
    //若身份证顺序码是996 997 998 999，说明是为百岁老人准备的特殊码
    if (array_search(substr($idCard, 12, 3), array('996', '997', '998', '999')) !== false) {
      $idCardBase = substr($idCard, 0, 6).'18'.substr($idCard, 6);
    } else {
      $idCardBase = substr($idCard, 0, 6).'19'.substr($idCard, 6);
    }

    return $idCardBase .= $this->getCnIDCheckCode($idCardBase);
  }

  /**
   * 验证18位的身份证号.
   */
  public function checkCnID($idCard, $convert = false)
  {
    if ($convert && strlen($idCard) == 15) {
      return $this->formatCnID($idCard);
    }
    if (strlen($idCard) != 18) {
      return false;
    }
    $idCardBase = substr($idCard, 0, 17);
    $checkCode = substr($idCard, 17, 1);
    if ($this->getCnIDCheckCode($idCardBase) != strtoupper($checkCode)) {
      return false;
    }

    return true;
  }

  /**格式化日期格式，支持xls时间*/
  public function formatDate($str, $maxYear = 50){
    if(preg_match('/(\\d{4})[\\-\\/\\.]+(\\d{1,2})[\\-\\/\\.]+(\\d{1,2})/',$str, $match)){
      $str = $match[1] . '-' . sprintf('%02d', $match[2]) . '-' . sprintf('%02d', $match[3]);
    }else if(preg_match('/(\\d{2})\\/(\\d{2})\\/(\\d{2}|\\d{4})/', $str, $match)){
      if(strlen($match[3]) == 2){
        if(intval($match[3]) > $maxYear){ // 年份后两位大于 50 已经足够了
          return '19' . $match[3] . '-' . $match[1] . '-' . $match[2];
        }else{
          return '20' . $match[3] . '-' . $match[1] . '-' . $match[2];
        }
      }else{
        return $match[3].'-'.$match[1].'-'.$match[2];
      }
    }else if(is_int($str)){
      return date('Y-m-d', ($str-19-70*365)*86400);
    }
    return null;
  }
  
  /*
  * 根据 https://en.wikipedia.org/wiki/ISO_8601 定义，
  * 一年的1月4号必在第一周 ，可求取相对第一周过去的天数，
  * 进而求取在1月4号后过去weeks-1周的某一个日期，
  * 最后可获得通过这个日期获得所在周的信息
  */
  public function getWeekInfo($weekth, $year){
    
    $add_days = ($weekth - 1) * 7;
    $that_time = strtotime("+{$add_days} day {$year}-01-04");
    
    return $this->getWeekInfoByUnixTime($that_time);
  }
  
  /**
  * 通过一个时间戳获得所在周的信息
  */
  public function getWeekInfoByUnixTime($unixtime = null){
    if(func_get_args() == 0){
      $unixtime = time();
    }
    $retData = array();
    
    $retData['week_start'] = strtotime('this week monday', $unixtime);
    $retData['week_end'] = strtotime('this week sunday', $unixtime);
    $retData['weekth'] = intval(date('W', $unixtime));
    $retData['year'] = date('Y', $unixtime);
    if(date('m', $unixtime) == '12' && $retData['weekth'] == 1){
      $retData['year'] = $retData['year'] + 1;
    }
    
    return $retData;
  }

  /**
   * money frome digit convert to cn.
   */
  public function rmbFormat($money = 0, $is_round = true, $int_unit = '元')
  {
    $chs = array(0, '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖');
    $uni = array('', '拾', '佰', '仟');
    $dec_uni = array('角', '分');
    $exp = array('', '万', '亿');
    $res = '';
    $parts = explode('.', $money, 2);
    $int = isset($parts [0]) ? strval($parts [0]) : 0;
    $dec = isset($parts [1]) ? strval($parts [1]) : '';
    $dec_len = strlen($dec);
    if (isset($parts [1]) && $dec_len > 2) {
      $dec = $is_round ? substr(strrchr(strval(round(floatval('0.'.$dec), 2)), '.'), 1) : substr($parts [1], 0, 2);
    }
    if (empty($int) && empty($dec)) {
      return '零';
    }
    for ($i = strlen($int) - 1, $t = 0; $i >= 0; ++$t) {
      $str = '';
      for ($j = 0; $j < 4 && $i >= 0; $j++, $i--) {
        $u = $int[$i]
        > 0 ? $uni [$j] : '';
        $str = $chs [$int [$i]].$u.$str;
      }
      $str = rtrim($str, '0');
      $str = preg_replace('/0+/', '零', $str);
      $u2 = $str != '' ? $exp [$t] : '';
      $res = $str.$u2.$res;
    }
    $dec = rtrim($dec, '0');
    if (!empty($dec)) {
      $res .= $int_unit;
      $cnt = strlen($dec);
      for ($i = 0; $i < $cnt; ++$i) {
        $u = $dec [$i]
        > 0 ? $dec_uni [$i] : '';
        $res .= $chs [$dec [$i]].$u;
      }
      if ($cnt == 1) {
        $res .= '整';
      }
      $res = rtrim($res, '0');
      $res = preg_replace('/0+/', '零', $res);
    } else {
      $res .= $int_unit.'整';
    }

    return $res;
  }

  /** 
   * 将阿拉伯数字换成中文汉字
   * 中国数字读法习惯每四位一组，分别为万、亿、万亿、亿亿，组内分别为十、百、千
   */
  public function getCnDigit($num){

    $cnNum = array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九');
    $cnUni = array('', '十', '百', '千'); //组内每位插入
    $cnGrp = array('', '万', '亿','万亿','亿亿'); //每组前插入
    
    $cnStr = '';
    $num = (string)$num;
    $len = strlen($num);
    $lastZero = true;
    $firstZero = true;
    $cnStr = '';// 返回结果
    for ($i=$len-1; $i >= 0 ; $i--) {
      $j = $len-$i;
      if($j%4 == 1 && $j != 1){
        $k = floor($j/4);
        if($firstZero == true && $cnGrp[$k] != ''){
          $l = strlen($cnGrp[$k-1]);
          $cnStr = substr($cnStr,$l);
        }
        $cnStr = $cnGrp[$k] . $cnStr;
        $firstZero = true;
        $lastZero = true;
      }
      if ($num[$i] == 0) {
        if (!$firstZero && !$lastZero) {
          $cnStr = $cnNum[0] . $cnStr;
          $lastZero = true;
        }
      }else{
        $cnStr = $cnNum[$num[$i]].$cnUni[($j-1)%4].$cnStr;
        $firstZero = false;
        $lastZero = false;
      }
    }
    return str_replace('一十', '十', $cnStr);
  }
  

  /** 一个真正意义上的zip压缩函数
   * @param $nodes 被压缩的文件夹路径
   * @param $zipDir 是否放在子目录中（即$dir在zip文件中的名字，可以没有）
   * @param $fileName 创建的zip文件路径名（含文件名）
   * eg: superZippy('D:\\new-zip.zip', array(
   *  array('path'=>'D:\fzjcxt\uploads\wjxx\', 'zipDir'=>'wjxx'), // 所有文件放到wjxx中
   *  array('path'=>'D:\fzjcxt\uploads\wjxx2\', 'zipDir'=>''), // 所有文件放在根目录
   *  array('path'=>'D:\fzjcxt\index.txt', 'zipDir'=>'home.txt') // index.txt 的内容放在home.txt
   * ));
  */
  function superZippy($fileName, $nodes){
    $zip = new ZipArchive();
    $zip->open($fileName, ZipArchive::OVERWRITE);
    $stack = array();
    foreach($nodes as $node){
      if(is_file($node['path'])){
        $lastZipDir = ($node['zipDir'] != '')? $node['zipDir'] : basename($node['path']);
        $zip->addFile($node['path'], $lastZipDir);
        //$zip->setCompressionName($lastZipDir, ZipArchive::CM_STORE); // 为节省时间，选择直接存储模式
      }else{
        if($node['zipDir'] != ''){
          $zip->addEmptyDir($node['zipDir']);
        }
        array_push($stack, $node);
      }
    }
    while($node = array_pop($stack)){
      $files = $this->fetchDir($node['path']);
      foreach($files['file'] as $file){
        $local = ($node['zipDir'] == '')? $file['name'] : $node['zipDir'] .'/'. $file['name'];
        $zip->addFile($file['path'], $local);
        //$zip->setCompressionName($local, ZipArchive::CM_STORE); // 为节省时间，选择直接存储模式
      }
      foreach($files['folder'] as $folder){
        $lastZipDir = ($node['zipDir'] == '')?$folder['name']:$node['zipDir'] .'/'. $folder['name'];
        $zip->addEmptyDir($lastZipDir);
        $folder['zipDir'] = $lastZipDir;
        array_push($stack, $folder);
      }
    }
    $zip->close();
  }



  /**
   * 获取pathinfo模式下的get变量，形式如：/key1/val1/key2/val2/...
   */
  public function urlRewrite($pathinfo = null)
  {
    $uri = $pathinfo ? $pathinfo : $_SERVER['PATH_INFO'];
    //$uri =  iconv('GBK', 'UTF-8', $uri);
    $params = explode('/', trim($uri, '/'));
    $ret = array();
    for ($i = 0;$i < count($params);$i += 2) {
      $ret[$params[$i]] = $params[$i + 1];
    }

    return $ret;
  }

  /**
   * 将文件尺寸转换为更适合人们阅读的形式
   * @param  long $bytes 文件字节数
   * @return string        带单位的文件长度
   */
  public function toHumanSize($bytes) {
    $units = 'BKMGTP';
    $factor = floor((strlen($bytes) - 1) / 3);
    $unit = ($units[$factor] == 'B')? 'B' : $units[$factor] . 'B';
    return sprintf("%.2f", $bytes / pow(1024, $factor)) . $unit;
  }

  public function paseHumanSize($size){
    $size = str_replace('B','',strtoupper($size));
    if(preg_match('/^([0-9.]+)([KMGTP])$/', $size, $match)){
      $pos = strpos('BKMGTP', $match[2]);
      $size = $match[1] * pow(2, 10 * $pos);
    }
    return $size;
  }
  
  
  /**
   * 将文件下载到服务器
   * @param  string $url      下载链接
   * @param  string $storePattern 下载路径
   * @param  array  $allowExt 允许的扩展名
   * @return array           返回下载信息
   */
  public function serverDownload ($url, $storePattern = 'utility', $allowExt = array(), $maxSize = '30MB'){
    $maxSize = $this->paseHumanSize($maxSize);
    if (empty($allowExt)) {
      $allowExt = array('gif', 'jpg', 'png', 'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'flv', 'mp4','html');
    }
    
    $path = parse_url($url, PHP_URL_PATH);
    $pathInfo = pathinfo($path);
    
    $content = $this->curlGet($url);
    $size = strlen($content);
    if ($size > $maxSize) {
      return array('error' => '1', 'link' => $url, 'message' => "下载失败，文件大小限制{$maxSize}Byte！");
    }
    
    if (!in_array($pathInfo['extension'], $allowExt)) {
      return array('error' => '1', 'link' => $url, 'message' => '下载失败，文件格式不符合要求！');
    }
    
    if(strpos($storePattern, '..') !== false){
      return array('error' => '1', 'link' => $url, 'message' => "存储路径不能含有“..”！");
    }
    if(!file_exists(UPLOAD_PATH) || !is_writable(UPLOAD_PATH)){
      return array('error' => '1', 'link' => $url, 'message' => '下载失败，存储目录不存在或无写权限！');
    }
    

    if(strpos($storePattern, '<') === false){
      $storePattern = $storePattern . '/<y/m/d-his-c>';
    }

    // 根据制定的规则替换样式代码
    $storePattern_ins = preg_replace_callback('/<(.+?)>/i', function($match){
      $microTimeSet = explode(' ', microtime());
      $seconds = $microTimeSet[1];
      $micsecs = str_replace('0.', '', $microTimeSet[0]);
      $micsecs = sprintf('%-06s', substr($micsecs,0,6));
      $ret = str_ireplace('y', date('Y',$seconds), $match[1]);
      $ret = str_ireplace('m', date('m', $seconds), $ret);
      $ret = str_ireplace('d', date('d', $seconds), $ret);
      $ret = str_ireplace('h', date('H', $seconds), $ret);
      $ret = str_ireplace('i', date('i', $seconds), $ret);
      $ret = str_ireplace('s', date('s', $seconds), $ret);
      $ret = str_ireplace('c', $micsecs, $ret);
      $pos = 0;
      while(true){
        $pos = stripos($ret, 'r', $pos); 
        if ($pos === false) {
          break;
        }
        $ret = substr_replace($ret, rand(0, 9), $pos, 1);
        $pos = $pos + 1;
      }
      return $ret;
    }, $storePattern);

    $uri = $storePattern_ins . '.' . $pathInfo['extension'];
    $file = UPLOAD_PATH . str_replace('\\/', DS, $uri);
    $filedir = dirname($file);
    if (!file_exists($filedir) && !mkdir($filedir, 0640, true)) {
      return array('error' => '1', 'link' => $url, 'message' => '下载失败，无法创建子目录！');
    }
    file_put_contents($file, $content);
    return array(
      'error' => '0',
      'uri' => $uri,
      'file' => $file,
      'origin' => $pathInfo['basename'],
      'size' => $size
    );
  }

  /**
   * 上传文件函数,支持多维度
   * 返回的错误代码（1:自定义错误, 2:没有上传文件, 3:NET & I/O错误, 4:不存在的上传动作）.
   * 存放路径及文件名的样式码替换规律如下
   * 1. 样式代码需要用尖括号<>括起来的，否则原样输出，最后为文件名样式。
   * 2. 每个 y 被替换为四位年份；
   * 3. 每个 m, d, h, i, s 分别被替换为两位的月份, 小时（24小时制）, 分钟, 秒数；
   * 4. c 被替换为微秒数（取秒的小数点后前6位数字，不足右补零）；
   * 5. 每个 r 被替换为一个单独的随机数。
   * 为了写代码方便，向后兼容等，路径中没有样式码的会自动添加 /<y/m/d-his-rrrrr> ！
   */
  public function upload($field, $storePattern = 'util', $allowExt = array('gif', 'jpg', 'png', 'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'flv', 'mp4'), $maxSize = '30MB'){
    // 准备工作(因路径中不能出现“?"*|<>:”等字符，故可用来代表特殊含义)
    if(strpos($storePattern, '<') === false){
      $storePattern = $storePattern . '/<y/m/d-his-rrrrrr>';
    }
    $maxSize = str_replace('B', '', strtoupper($maxSize));
    if(preg_match('/^([0-9.]+)([KMGTP])$/', $maxSize, $match)){
      $pos = strpos('BKMGTP', $match[2]);
      $maxSize = $match[1] * pow(2, 10 * $pos);
    }

    // 一些在开发、部署阶段出现的错误
    if(!file_exists(UPLOAD_PATH) || !is_writable(UPLOAD_PATH)){
      return array('error' => '1', 'message' => '上传失败，附件根目录不存在或无写权限！..”！');
    }
    if(strpos($storePattern, '..') !== false){
      return array('error' => '1', 'message' => '上传失败，存储路径不能含有“..”！');
    }
    if($_FILES[$field] == null){
      return array('error' => '4', 'message' => '上传失败，未发现上传动作，请检查form表单！');
    }

    $uploadRstSet = array();
    $stack = array();
    // 1. 首先以name为探针，采用横向遍历树的方式
    // keys 存储在维度上的下标位置信息
    $stackHead = array('keys'=>array(), 'name'=>$_FILES[$field]['name']);
    array_push($stack, $stackHead);
    while(!empty($stack)){
      $stackHead = array_pop($stack);
      if(is_array($stackHead['name'])){
        foreach($stackHead['name'] as $key=>$val){
          $keys = $stackHead['keys'];
          array_push($keys, $key);
          array_unshift($stack, array('keys'=>$keys, 'name'=>$val));
        }
      }else{
        // 2. 然后利用name的在维度上的位置下标信息，
        // 来获取error,tmp_name... 的值
        // 并以相同的维度结构存储上传结果信息
        $uploadRst = &$uploadRstSet;
        $name = $stackHead['name'];
        $tmp_name = $_FILES[$field]['tmp_name'];
        $error = $_FILES[$field]['error'];
        $size = $_FILES[$field]['size'];
      
        foreach($stackHead['keys'] as $it_key){
          $tmp_name = $tmp_name[$it_key];
          $error = $error[$it_key];
          $size = $size[$it_key];
          if(!isset($uploadRst[$it_key])){
            $uploadRst[$it_key] = null;
          }
      
          $uploadRst = &$uploadRst[$it_key];
        }

        // 数据检查
        $error = intval($error);
        $size = intval($size);
        $name = $this->clearHideChar($name);

        if ($error == 4) {
          $uploadRst = array('error' => '2', 'message' => '没有需上传的文件，请选择文件再上传！');
          continue;
        } elseif ($error > 0) {
          $uploadRst = array('error' => '3', 'message' => '上传失败，错误代码：'.$error.'，请联系系统管理员！');
          continue;
        }
        if ($size > $maxSize) {
          $uploadRst = array('error' => '1', 'message' => "上传失败，文件大小限制{$maxSize}Byte！");
          continue;
        }
        if (!is_uploaded_file($tmp_name)) {
          $uploadRst = array('error' => '1', 'message' => '上传失败，拒绝访问其他临时文件！');
          continue;
        }
        $fileExt = strtolower(mb_substr(mb_strrchr($name, '.'), 1));
        if (!in_array($fileExt, $allowExt)) {
          $uploadRst = array('error' => '1', 'message' => '上传失败，文件格式不符合要求！');
          continue;
        }
        // 根据制定的规则替换样式代码
        $storePattern_ins = preg_replace_callback('/<(.+?)>/i', function($match){
          $microTimeSet = explode(' ', microtime());
          $seconds = $microTimeSet[1];
          $micsecs = str_replace('0.', '', $microTimeSet[0]);
          $micsecs = sprintf('%-06s', substr($micsecs,0,6));
          $ret = str_ireplace('y', date('Y',$seconds), $match[1]);
          $ret = str_ireplace('m', date('m', $seconds), $ret);
          $ret = str_ireplace('d', date('d', $seconds), $ret);
          $ret = str_ireplace('h', date('H', $seconds), $ret);
          $ret = str_ireplace('i', date('i', $seconds), $ret);
          $ret = str_ireplace('s', date('s', $seconds), $ret);
          $ret = str_ireplace('c', $micsecs, $ret);
          $pos = 0;
          while(true){
            $pos = stripos($ret, 'r', $pos); 
            if ($pos === false) { 
              break; 
            }
            $ret = substr_replace($ret, rand(0, 9), $pos, 1);
            $pos = $pos + 1;
          }
          return $ret;
        }, $storePattern);

        $uri = $storePattern_ins . '.' . $fileExt;
        $file = UPLOAD_PATH . str_replace('/', DS, $uri);
        $filedir = dirname($file);

        if (!file_exists($filedir) && !mkdir($filedir, 0640, true)) {
          $uploadRst = array('error' => '1', 'message' => '上传失败，无法创建子目录！');
          continue;
        }
        if (file_exists($file) || !move_uploaded_file($tmp_name, $file)) {
          $uploadRst = array('error' => '1', 'message' => '上传失败，文件写入出错！');
          continue;
        };

        $uploadRst = array(
          'error' => '0',
          'uri' => $uri,
          'file' => $file,
          'origin' =>$name,
          'size' => $size,
          'ext' => $fileExt
        );

      } // end single upload
      
    } // end while
    return $uploadRstSet;
  }

  /**
   * 上传文件到指定位置
   * 返回的错误代码（1:自定义错误, 2:没有上传文件, 3:NET & I/O错误, 4:不存在的上传动作）.
   */
  public function uploadTo($field, $file, $allowExt = array('gif', 'jpg', 'png', 'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'flv', 'mp4'), $maxSize = '30MB'){
   
    $maxSize = str_replace('B', '', strtoupper($maxSize));
    if(preg_match('/^([0-9.]+)([KMGTP])$/', $maxSize, $match)){
      $pos = strpos('BKMGTP', $match[2]);
      $maxSize = $match[1] * pow(2, 10 * $pos);
    }

    
    if($_FILES[$field] == null){
      return array('error' => '4', 'message' => '上传失败，未发现上传动作，请检查form表单！');
    }
   
    $name = $_FILES[$field]['name'];
    $tmp_name = $_FILES[$field]['tmp_name'];
    $error = $_FILES[$field]['error'];
    $size = $_FILES[$field]['size'];
  
    $name = $this->clearHideChar($name);

    if ($error == 4) {
      return array('error' => '2', 'message' => '没有需上传的文件，请选择文件再上传！');
    }
    if ($error > 0) {
      return array('error' => '3', 'message' => '上传失败，错误代码：'.$error.'，请联系系统管理员！');
    }
    if ($size > $maxSize) {
      return array('error' => '1', 'message' => "上传失败，文件大小限制{$maxSize}Byte！");
    }
    if (!is_uploaded_file(realpath($tmp_name))) {
      return array('error' => '1', 'message' => '上传失败，拒绝访问其他临时文件！');
    }
    $fileExt = strtolower(mb_substr(mb_strrchr($name, '.'), 1));
    if (!in_array($fileExt, $allowExt)) {
      return array('error' => '1', 'message' => '上传失败，文件格式不符合要求！');
    }
    $filedir = dirname($file);
    if (!file_exists($filedir) && !mkdir($filedir, 0640, true)) {
      return array('error' => '1', 'message' => '上传失败，无法创建子目录！');
    }
    if (!move_uploaded_file($tmp_name, $file)) {
      return array('error' => '1', 'message' => '上传失败，文件写入出错！');
    };

    return array(
      'error' => '0',
      'file' => $file,
      'origin' =>$name,
    );
  }

  /**
  * 上传动作不确定上传数目的情况下，直接上传全部或有前缀的部分.
  */
  public function uploadAll($prefix = null, $storePattern = 'util', $allow = array(), $maxSize = '30MB')
  {
    $ret = array();
    foreach ($_FILES as $key => &$val) {
      if ($prefix == null || ($prefix != null && strpos($key, $prefix) === 0)) {
        $ret[$key] = $this->upload($key, $storePattern, $allow, $maxSize);
      }
    }
    return $ret;
  }

  /**
   * 生成缩略图
   * 本函数支持透明通道，等比例缩放，默认对小图片不作处理.
   *
   * @param string $src     文件路径
   * @param string $dst     保存路径
   * @param integer $width  缩放后最大宽度
   * @param integer $height 缩放后最大高度
   * @param bool $enlarge   是否允许放大
   * @return void
   */
  public function thumb($src, $dst = null, $width = 1440, $height = 900, $enlarge = false)
  {
    if (!file_exists($src) || filesize($src) < 3) {
      trigger_error('Invalid file path!!', E_USER_ERROR);
    }
    // 定义数据
    $supported = array('gif', 'jpeg', 'jpg', 'png', 'bmp');
    $headTypes = array(
      6677    => 'bmp',
      7173    => 'gif',
      13780   => 'png',
      255216  => 'jpg',
    );
    // 获取图片的实际格式
    $topTwoBytes = file_get_contents($src, NULL, NULL, 0, 2);
    $imageHead = unpack('C2char', $topTwoBytes);
    $typeCode = intval($imageHead['char1'].$imageHead['char2']);
    $realExt = $headTypes[$typeCode];
    if (!in_array($realExt, $supported)) {
      trigger_error('Unsupported image file:'.$realExt, E_USER_ERROR);
    }
    // 根据图片实际格式来加载文件内容
    switch ($realExt) {
      case 'bmp':
        $img = imagecreatefromwbmp($src);
        break;
      case 'gif':
        $img = imagecreatefromgif($src);
        break;
      case 'jpeg':
      case 'jpg':
        $img = imagecreatefromjpeg($src);
        break;
      case 'png':
        $img = imagecreatefrompng($src);
        break;
    }
    // 按原比例缩放图片
    list($w, $h) = getimagesize($src);
    if($enlarge){
      $ratio = min($width / $w, $height / $h); 
    }else{
      $ratio = min(1, $width / $w, $height / $h); // 小图不动
    }

    $width = $w * $ratio;
    $height = $h * $ratio;

    $canvas = imagecreatetruecolor($width, $height);
    if ($realExt == 'gif' or $realExt == 'png') {
      imagecolortransparent($canvas, imagecolorallocatealpha($canvas, 0, 0, 0, 127));
      imagealphablending($canvas, false);
      imagesavealpha($canvas, true);
    }
    // 按像素复制图片
    imagecopyresampled($canvas, $img, 0, 0, 0, 0, $width, $height, $w, $h);

    // 按指定格式来生成最终文件
    if($dst != null){
      $realExt = strtolower(substr(strrchr($dst, '.'), 1));
      if ($realExt == '' || !in_array($realExt, $supported)) {
        trigger_error('can\'t convert to unsupported image!!', E_USER_ERROR);
      }
    }else{
      $dst = $src;
    }
    switch ($realExt) {
      case 'bmp':
        return imagewbmp($canvas, $dst);
      case 'gif':
        return imagegif($canvas, $dst);
      case 'jpeg':
      case 'jpg':
        return imagejpeg($canvas, $dst);
      case 'png':
        return imagepng($canvas, $dst);
    }
  }

  /**
   * 生成灰度图片.
   *
   * @return bool 是否成功
   */
  public function imageGray($src, $dst)
  {
    if (!file_exists($src)) {
      trigger_error('Invalid file path!!', E_USER_ERROR);
    }
    $ext = strtolower(substr(strrchr($src, '.'), 1));
    $origin = array('gif', 'jpeg', 'jpg', 'png', 'bmp');
    if (!in_array($ext, $origin)) {
      trigger_error('Unsupported image file!!', E_USER_ERROR);
    }
    switch ($ext) {
      case 'bmp':
        $img = imagecreatefromwbmp($src);
        break;
      case 'gif':
        $img = imagecreatefromgif($src);
        break;
      case 'jpeg':
      case 'jpg':
        $img = imagecreatefromjpeg($src);
        break;
      case 'png':
        $img = imagecreatefrompng($src);
        break;
      default:
        return false;
    }
    if (imageistruecolor($img)) {
      imagetruecolortopalette($img, false, 256); //如果是真彩色图象，将真彩色图像转换为调色板图像
    }
    for ($i = 0; $i < imagecolorstotal($img); ++$i) { /*获得调色板中颜色的数目*/
      $rgb = imagecolorsforindex($img, $i); //获得颜色i点的颜色值
      $gray = round(0.229 * $rgb['red'] + 0.587 * $rgb['green'] + 0.114 * $rgb['blue']); //获得颜色灰度值
      imagecolorset($img, $i, $gray, $gray, $gray); //设置i点颜色值
    }
    switch ($ext) {
      case 'bmp':
        return imagewbmp($img, $dst);
      case 'gif':
          return imagegif($img, $dst);
      case 'jpeg':
      case 'jpg':
        return imagejpeg($img, $dst);
      case 'png':
        return imagepng($img, $dst);
      default:
        return false;
    }
  }

  /**
   * 返回当前文件夹中的文件名及子文件夹名
   */
  public function fetchDir($path)
  {
    $path = $this->to_gb_lt71($path);
    $path = rtrim(preg_replace('/\\\\\\//', DS, $path), DS) . DS;
    $retval = array('folder'=>array(), 'file'=>array());
    $handler = opendir($path);
    while (false !== ($dir = readdir($handler))) {
      if ($dir != '.' && $dir != '..') {
        $file = $path . $this->to_utf_lt71($dir);
        if (is_dir($file)) {
          $retval['folder'][] = array('name'=>$dir, 'path'=>$file);
        } else if (is_file($file)) {
          $retval['file'][] = array('name'=>$dir, 'path'=>$file);
        }
      }
    }
    closedir($handler);

    return $retval;
  }

  /**
   * 递归删除文件夹及其内容和子文件夹
   */
  public function removeDir($path)
  {
    $path = $this->to_gb_lt71($path);
    $path = rtrim(preg_replace('/\\\\\\//', DS, $path), DS) . DS;
    $handler = opendir($path);
    while (false !== ($dir = readdir($handler))) {
      if ($dir != '.' && $dir != '..') {
        $file = $path . $this->to_gb_lt71($dir);
        if (is_dir($file)) {
          $this->{__FUNCTION__}($file);
        } elseif (is_file($file)) {
          unlink($file);
        }
      }
    }
    closedir($handler);
    rmdir($path);
  }

  /**
   * 移除附件（用于删除数据库后删除附件文件，多个路径支持竖线分割）
   */
  public function delAttach($paths){
    $pathSet = $this->explode('|', $paths);
    foreach($pathSet as $path){
      // 检测是否是相对路径(只考虑linux/windows)
      if(strpos($path,'/') !== 0 && strpos($path, ':') === false){
        $path = UPLOAD_PATH . $path;
      }
      $path = str_replace('/', DS, $path);
      if(is_file($path)){
        unlink($path);
      }
    }
  }

  /** php7.1之前路径编码为gb2312 */
  public function to_gb_lt71($path){
    if(php_uname('s') == 'Windows NT' && version_compare(PHP_VERSION, '7.1.29') < 0 ){
      $path = mb_convert_encoding($path, 'gb2312', 'UTF-8');
    }
    return $path;
  }

  /** php7.1之前路径编码为gb2312 */
  public function to_utf_lt71($path){
    if(php_uname('s') == 'Windows NT' && version_compare(PHP_VERSION, '7.1.29') < 0 ){
      $path = mb_convert_encoding($path, 'UTF-8', 'gb2312');
    }
    return $path;
  }

  /** 将数据保存成csv文件（给什么存什么）*/
  public function dataToCsv($data, $fileName, $encoding ='GBK'){
    $retVal = '';
    foreach($data as &$record){
      foreach ($record as $key => &$value) {
        //有特殊字符的数据用双引号包裹，双引号自身用双引号转义
        if(strpos($record[$key], ',') !== false
        || strpos($record[$key], CRLF) !== false
        || strpos($record[$key], LF) !== false
        || strpos($record[$key], '"') !== false){
          $record[$key]= str_replace('"', '""', $value);
          $record[$key] = '"' . $record[$key] . '"';
        }
      }
      $retVal .= implode(',', $record) . CRLF;
    }
    if(strtoupper($encoding) != 'UTF-8'){
      $retVal = mb_convert_encoding($retVal, $encoding, "UTF-8");
    }
    $this->forceDownload($fileName, $retVal);
  }
  
  /** 将数据保存成csv文件（使用第一行作为列参考，过滤掉其他行多余的列数据,大数字自动加撇号）*/
  public function dataToCsvFixed($data, $fileName, $encoding ='GBK'){
    $retVal = '';
    $colSet = is_array($data[0])?array_keys($data[0]):array();
    foreach($data as &$record){
      $_tmp = array();
      foreach ($colSet as $key) {
        $_tmp[$key] = $record[$key];
        // 遍历每一个数据项，如果是纯数字且位数超过11，前面加撇号，防止Excel使用科学计数
        // 对于5454e3这样的字符串也要防止Excel认为是科学计数(5454000)
        if(is_numeric($_tmp[$key]) 
        && (strlen($_tmp[$key])>11 || stripos($_tmp[$key], 'e') !== false)){
          $_tmp[$key] = '\'' . $_tmp[$key];
        }
        // 有特殊字符的数据用双引号包裹，双引号自身用双引号转义
        if(strpos($_tmp[$key], ',') !== false 
        || strpos($_tmp[$key], CRLF) !== false
        || strpos($_tmp[$key], LF) !== false
        || strpos($_tmp[$key], '"') !== false){
          $_tmp[$key]= str_replace('"', '""', $_tmp[$key]);
          $_tmp[$key] = '"' . $_tmp[$key] . '"';
        }
      }
      $retVal .= implode(',', $_tmp) . CRLF;
    }
    // @todo add "\xEF\xBB\xBF";
    if(strtoupper($encoding) != 'UTF-8'){
      $retVal = mb_convert_encoding($retVal, $encoding, "UTF-8");
    }
    $this->forceDownload($fileName, $retVal);
  }
  /*
  * 读取csv文件到一个二维数组（时间复杂度n）
  * csv要求使用逗号来分隔内容，使用双引号包裹换行及逗号等特殊字符！
  * utf-8编码方式不与ascii码发生冲突，所以直接进行字节搜索是安全的
  * utf-8 1字节字符编码 0xxxxxxx（即ascii）
  * utf-8 2字节字符编码 110xxxxx 10xxxxxx
  * utf-8 3字节字符编码 1110xxxx 10xxxxxx 10xxxxxx
  * ...
  */
  public function dataFromCsv($str, $encoding = 'GBK'){
    $quotes = 0;
    // 返回的数据（二维数组）
    $retData = array();
    // csv文件中的一行数据
    $record = array();
    // csv中的一个元素中的字符
    $queue = array();
    if(strtoupper($encoding) != 'UTF-8'){
      $str = mb_convert_encoding($str, "UTF-8", $encoding);
    }
    $len = strlen($str);
    for($i = 0; $i < $len; $i++){
      $char = $str[$i];
      $char_next = '';
      // 获取下一个字符
      if($i+1 < $len){
        $char_next = $str[$i+1];
      }
      // 碰到一个逗号怎么处理
      if($char == ','){
        // 逗号在引号对外面如："xxx""", 或 "xxxxx", 或 xx1,xx2
        if($quotes % 2 == 0){
          $val = implode('', $queue);
          if($quotes > 0){
            $val = substr($val, 1, strlen($val)-2);
          }
          // 反转义内容里面的引号，并存储
          $record[] = str_replace('""', '"', $val);
          $queue = array();
          $quotes = 0;
          // 假如文件末尾没有CRLF，且最后一个字符是逗号
          if($i+1 == $len){
            $record[] = '';
            $retData[] = $record;
          }
        }else{
          // 此逗号属于正文部分
          $queue[] = ',';
        }
      }else if($char . $char_next == CRLF){
        // 同理，CRLF处理也要看换行符在不在引号里面
        if($quotes % 2 == 0){
          $val = implode('', $queue);
          if($quotes > 0){
            $val = substr($val, 1, strlen($val)-2);
          }
          // 反转义内容里面的引号，并存储
          $record[] = str_replace('""', '"', $val);
          $queue = array();
          // 结束一行并开始新的行（数组）
          $retData[] = $record;
          $record = array();
          $quotes = 0;
          // 最后，最后两个字符是CRLF是正常的不需要特殊处理
        }else{
          // 此换行符属于正文部分
          $queue[] = CRLF;
        }
        // 索引向前推进一位（CRLF由两个字符构成）
        $i++;
      }else{
        // 引号计数加一
        if($char == '"'){
          $quotes++;
        }
        $queue[] = $char;
        // 假如文件末尾没有CRLF，遍历到最后一个字符应：
        if($i+1 == $len){
          $val = implode('', $queue);
          // 最后一个字符是引号才有必要检测
          if($char == '"'){
            $val = substr($val, 1, strlen($val)-2);
          }
          $record[] = str_replace('""', '"', $val);
          $retData[] = $record;
        }
      }
    }

    return $retData;
  }
  
  /**
   * 将表格数据根据给出的表头转换成关联数组（二维）,如果表头没给出（第二参数）则表头为第一行数据
   * 本函数适用于从csv或excel直接读取的数据
   * */
  public function parseTableToAssoc($fromData, $ColMap = array(), $start = 1){
    $colUnfindSet = array();
    $colKeyToIndexMap = array();
    $retData = array();
    if(count($ColMap)>0){
      foreach($ColMap as $colKey => &$colName){
        $found = false;
        for($colIndex = 0; $colIndex < count($fromData[0]); ++$colIndex) {
          $cellVal = trim($fromData[0][$colIndex]);
          if($cellVal == $colName || $cellVal == $colKey){
            $colKeyToIndexMap[$colKey] = $colIndex;
            $found = true;
            break;
          }
        }
        if($found == false){
          $colUnfindSet[] = $colName;
        }
      }
      if(count($colUnfindSet)>0){
        $colUnfindStr=implode('、', $colUnfindSet);
        return array('error'=>1, 'message'=>'第一行未找到列：'.$colUnfindStr, 'data'=>null);
      }
    }else {
      // 未提供列名与字段名的对应关系，则默认第一行即为字段名
      for($colIndex = 0; $colIndex < count($fromData[0]); ++$colIndex) {
        $cellVal = trim($fromData[0][$colIndex]);
        if($cellVal != ''){
          $colKeyToIndexMap[$cellVal] = $colIndex;
        }
      }
    }
    // 舍弃第一行（第一行为列标题，无实际意义）
    for($rowIndex = $start; $rowIndex < count($fromData); ++$rowIndex) {
      $record = array();
      foreach($colKeyToIndexMap as $colKey => $mapIndex) {
        $record[$colKey] = $fromData[$rowIndex][$mapIndex];
      }
      $retData[] = $record;
    }
    return array('error'=>0, 'message'=>'转换成功', 'data'=>$retData);
  }

  /**
   * 返回包含指定键值集合的部分数组，$reverse反选.
   */
  public function subSet($array, $keySet, $reverse = false)
  {
    $rtn = array();
    foreach ($keySet as &$key) {
      if (array_key_exists($key, $array)) {
        $rtn[$key] = $array[$key];
      }
    }
    if ($reverse) {
      return array_diff_key($array, $rtn);
    }

    return $rtn;
  }
  /**
  * 在一个一维数组中删除目标值
  */
  public function arrayDel($arr, $val){
    while(($key = array_search($val ,$arr)) !== false){
      array_splice($arr, $key, 1);
    }
    return $arr;
  }
  /**
  * 将一个二维数组对制定的两列分别做乘积并求和
  */
  public function arrayProductSum($a, $k1, $k2){
    $s = 0;
    foreach($a as $r){
      if(is_numeric($r[$k1]) && is_numeric($r[$k2])){
        $s += $r[$k1] * $r[$k2];
      }
    }
    return $s;
  }

  /**
   * 将从数据库中返回的多行数据(二维数组)按字段（列）求和，返回一维数组.
   * 适用于统计表格的合计行
   */
  public function multiSum($data)
  {
    $retData=array();
    foreach ($data as &$record) {
      foreach ($record as $key => &$val) {
        if(is_numeric($val)){
          $retData[$key] += $val;
        }
      }
    }

    return $retData;
  }

  /**
   * 将多次数据查询的结果集（已合并）去掉重复记录,使用主键ID进行判断.
   */
  public function multiUnique($data, $primaryKey = 'id')
  {
    $keySet = array();
    foreach ($data as $index => &$record) {
      if (in_array($record[$primaryKey], $keySet)) {
        unset($data[$index]);
        continue;
      }
      array_push($keySet, $record[$primaryKey]);
    }

    return $data;
  }

  /**
   * 使用array_multisort对二维数组排序，适用于多次数据查询合并的结果集
   * 在使用时应确保记录无重复，建议使用$this->multiUnique过滤.
   */
  public function multiSort($data, $key, $order = SORT_ASC, $type = SORT_NUMERIC)
  {
    $keySet = array();
    foreach ($data as &$record) {
      $keySet[] = $record[$key];
    }
    array_multisort($keySet, $order, $type, $data);

    return $data;
  }

  /**
   * 将二维数组的的一列剥离出来，并进行矩阵转置.
   * 自php5.5起可使用array_column函数代替
   */
  public function arrayColumn($data, $valCol, $indexCol= null)
  {
    $ret = array();
    foreach ($data as $rowKey => &$rowData) {
      $key= ($indexCol !== null)? $rowData[$indexCol] : $rowKey;
      $ret[$key] = $rowData[$valCol];
    }

    return $ret;
  }
  
  /** 返回二维数组指定的列等于某值的行数（注意本函数中 null === ''） */
  public function arrayColumnCount($data, $indexCol, $colVal){
    $total = 0;
    foreach ($data as &$rowData) {
      if(strval($rowData[$indexCol]) === strval($colVal)){
        $total++;
      }
    }

    return $total;
  }
  
  /**
   * 返回二维数组指定的列的和
   */
  public function arrayColumnSum($data, $indexCol)
  {
    $sum = 0;
    foreach ($data as &$rowData) {
      $sum += $rowData[$indexCol];
    }

    return $sum;
  }

  /** 将数据插入到数组的第一个位置（支持键值）*/
  public function arrayUnshiftAssoc($arr, $val, $key = null){
    if($key === null){
      array_unshift($arr, $val);
      return $arr;
    }
    $reverse = array_reverse($arr, true);
    $reverse[$key] = $val;
    return array_reverse($reverse, true);
  }

  /** 从数组中随机选几个值 */
  public function arrayRand($data, $num = 1){
    shuffle($data);
    $result = array();
    $max = count($data);
    for ($i = 0; $i < $num && $i<$max; $i++) {
      $result[] = $data[$i];
    }
    if($max < $num){
      for ($i = 0; $i < $num-$max; $i++) {
        $result[] = null;
      }
    }
    return $result;
  }



  /**
   * 矩阵转置操作.
   */
  public function transposeMatrix($matrix)
  {
    $ret = array();
    foreach ($matrix as $i => &$row) {
      foreach ($row as $j => &$cell) {
        $ret[$j][$i] = $cell;
      }
    }

    return $ret;
  }

  /**
   * 同trim类似的功能但本系列函数仅删除匹配的边界一次
   *
   * @param string $string    被清理的字符串
   * @param string $delimiter 要清理的字符串
   *
   * @return string 返回后的字符串
   */
  public function trimOne($string, $delimiter)
  {
    $tmp = $this->ltrimOne($string, $delimiter);

    return $this->rtrimOne($tmp, $delimiter);
  }

  public function rtrimOne($string, $delimiter)
  {
    $string = trim($string);
    $length = strlen($delimiter);
    if (substr($string, strlen($string) - $length, $length) == $delimiter) {
      return substr($string, 0, strlen($string) - $length);
    }

    return $string;
  }
  public function ltrimOne($string, $delimiter)
  {
    $string = trim($string);
    $length = strlen($delimiter);
    if (substr($string, 0, $length) == $delimiter) {
      return substr($string, $length);
    }

    return $string;
  }

  /**
   * 判断是否在集合中，仅用于sql数字集合.
   */
  public function inStrSet($needle, $mixedSet, $split = ','){
    if(!is_array($mixedSet)){
      $mixedSet = $this->explode($split, $mixedSet);
    }
    
    return in_array($needle, $mixedSet);
  }

  public function allInStrSet($needleSet, $mixedSet, $split = ','){
    if(!is_array($needleSet)){
      $needleSet = $this->explode($split, $needleSet);
    }
    if(!is_array($mixedSet)){
      $mixedSet = $this->explode($split, $mixedSet);
    }
    foreach($needleSet as $item){
      if(!in_array($item, $mixedSet)){
        return false;
      }
    }
    return true;
    
  }
  public function partInStrSet($needleSet, $mixedSet, $split = ','){
    if(!is_array($needleSet)){
      $needleSet = $this->explode($split, $needleSet);
    }
    if(!is_array($mixedSet)){
      $mixedSet = $this->explode($split, $mixedSet);
    }
    foreach($needleSet as $item){
      if(in_array($item, $mixedSet)){
        return true;
      }
    }
    return false;
    
  }
  
  /**
   * 格式化数据用于MySQL in子句(内容中的引号已经转义).
   */
  function getStrSet($data, $nullret = '-1', $del='', $split = ','){
    if($data === null || $data === array() || $data === ''){
      return $nullret;
    }
    if(is_scalar($data)){
      $data = $this->explode($split, $data);
    }
    $data = array_unique($data);
    // 转义边界特殊字符
    if($del != ''){
      foreach($data as $key => $val){
        $data[$key] = $del . str_replace('\'', '\'\'', $val) . $del;
      }
    }else{
      foreach($data as $key => $val){
        $data[$key] = str_replace('\'', '\'\'', $val);
      }
    }
    
    return $this->implode($split, $data);
  }

  /** 语法糖，将某（些）值从字符串形式的集合中删除 */
  public function strSetDel($str, $del, $split = ','){
    $set = $this->explode($split, $str);
    $delset = $this->explode($split, $del);
    foreach($delset as $del){
      $set = $this->arrayDel($set, $del);
    }
    return $this->implode($split, $set);
  }

  /** 语法糖，将某（些）值添加到字符串形式的集合中 */
  public function strSetAdd($str, $add, $split = ','){
    $set = $this->explode($split, $str);
    $addset = $this->explode($split, $add);
    $set = array_unique(array_merge($set, $addset));
    return $this->implode($split, $set);
  }

  public function getSetByKeyPrefix($data, $prefix){
    $retData = array();
    foreach($data as $key=>$val){
      if($this->startWith($key, $prefix)){
        $retData[] = $val;
      }
    }
    return $retData;
    
  }

  /**
   * 将一个url的quergString部分解析为键值对数组.
   */
  public function parseQuery($url)
  {
    $tmp = array();
    $info = parse_url($url);
    parse_str($info['query'], $tmp);

    return $tmp;
  }

  /**
   * form 表单单行本输入框.
   */
  public function formTextInput($name, $cat, $val = null, $inputRawAttr = null)
  {
    
    if ($val !== null && is_array($val)) {
      $key_name = $name;
      if($key_pos = strpos($key_name, '[')){
        $key_name = substr($key_name, 0, $key_pos);
      }
      $val = ($key_name == '') ? '' : $val[$key_name];
    }
    if ($name != null) {
      $inputRawAttr .= " name=\"{$name}\"";
    }
    $val = htmlspecialchars($val);
    if(strtolower(trim($cat)) == 'file'){

      return "<input type=\"{$cat}\" name=\"{$name}\" data-val=\"{$val}\" {$inputRawAttr} />";
    }

    return "<input type=\"{$cat}\" value=\"{$val}\" {$inputRawAttr} />";
  }

  /**
   * form 表单多行文本输入区.
   */
  public function formTextArea($name, $val = null, $textAreaRawAttr = null)
  {
    
    if ($val !== null && is_array($val)) {
      $key_name = $name;
      if($key_pos = strpos($key_name, '[')){
        $key_name = substr($key_name, 0, $key_pos);
      }
      $val = ($key_name == '') ? '' : $val[$key_name];
    }
    if ($name != null) {
      $textAreaRawAttr .= " name=\"{$name}\"";
    }
    $val = htmlspecialchars($val);

    return "<textarea {$textAreaRawAttr}>{$val}</textarea>";
  }

  /**
   * form 表单下拉列表.
   */
  public function formSelect($name, $data, $selectedVal = null, $selectRawAttr = null, $optRawAttr = null)
  {
    if ($selectedVal !== null && is_array($selectedVal)) {
      $key_name = $name;
      if($key_pos = strpos($key_name, '[')){
        $key_name = substr($key_name, 0, $key_pos);
      }
      $selectedVal = ($key_name == '') ? '' : $selectedVal[$key_name];
    }
    if ($name != null) {
      $selectRawAttr .= " name=\"{$name}\"";
    }
    $formStr = "<select {$selectRawAttr}>" . CRLF;
    if(is_scalar($data)){ // 加载数据字典
      $dicData = $this->loadDic();
      $data = $dicData[$data];
    }
    // 如果集合中没有否定值，则给预设一个
    if($data[''] === null && $data[0] === null){
      $data = $this->arrayUnshiftAssoc($data, '请选择...', '');
    }
    foreach ($data as $key => &$val) {
      $key = htmlspecialchars($key);
      if(is_array($val)){
        $formStr .= "<optgroup label=\"{$key}\">" . CRLF;
        foreach ($val as $real_key => &$real_val) {
          if(is_numeric($real_key) && is_numeric($selectedVal)){
            $select = ($real_key == $selectedVal) ? 'selected' : null;
          }else{
            $select = (strval($real_key) === strval($selectedVal)) ? 'selected' : null;
          }
          $real_key = htmlspecialchars($real_key);
          $real_val = strip_tags($real_val);
          $real_val = htmlspecialchars($real_val);
          $formStr .= "<option {$select} value=\"{$real_key}\" {$optRawAttr}>{$real_val}</option>" . CRLF;
        }
        $formStr .= "</optgroup>" . CRLF;
       }else{
         if(is_numeric($key) && is_numeric($selectedVal)){
          $select = ($key == $selectedVal) ? 'selected' : null;
        }else{
          $select = (strval($key) === strval($selectedVal)) ? 'selected' : null;
        }
        $val = strip_tags($val);
        $val = htmlspecialchars($val);
        $formStr .= "<option {$select} value=\"{$key}\" {$optRawAttr}>{$val}</option>" . CRLF;
      }

    }

    return $formStr.'</select>';
  }

  /**
   * form 表单单选按钮组.
   */
  public function formRadio($name, $data, $checkedVal = null, $radioRawAttr = null, $labelRawAttr = null)
  {
   
    $formStr = null;
    if(is_scalar($data)){ // 加载数据字典
      $dicData = $this->loadDic();
      $data = $dicData[$data];
    }
    foreach ($data as $key => &$label) {

      $radioRawAttr_item = $radioRawAttr;

      if ($name != null) {
        $radioRawAttr_item .= " name=\"{$name}\"";
      }
      if (is_array($checkedVal)) {
        $checked = ($key == $checkedVal[$name]) ? 'checked' : null;
      } else {
        $checked = (strval($key) === strval($checkedVal)) ? 'checked' : '';
      }

      $key = htmlspecialchars($key);
      $label = htmlspecialchars($label);
      $formStr .= "<label {$labelRawAttr} ><input type=\"radio\" {$checked} value=\"{$key}\" {$radioRawAttr_item} /> {$label} </label>&emsp;";
    }

    return preg_replace('/^(.+?)(?:&emsp;)*$/', '${1}', $formStr);
  }

  /**
   * form 表单复选框组.注意第三个参数同其他form函数不一样.
   */
  public function formCheckBox($name, $data, $set = array(), $boxRawAttr = null, $labelRawAttr = null)
  {
   
    $formStr = null;
    $set = is_array($set)? $set : $this->explode(',', $set);
    if(is_scalar($data)){ // 加载数据字典
      $dicData = $this->loadDic();
      $data = $dicData[$data];
    }
    foreach ($data as $key => &$label) {
     
      $boxRawAttr_item = $boxRawAttr;
      if ($name != null) {
        $boxRawAttr_item .= " name=\"{$name}[]\"";
      }
      $checked = (in_array($key, $set)) ? 'checked' : null;
      $key = htmlspecialchars($key);
      $label = htmlspecialchars($label);
      $formStr .= "<label {$labelRawAttr} ><input type=\"checkbox\" {$checked} value=\"{$key}\" {$boxRawAttr_item} /> {$label} </label> &emsp;";
    }

    return preg_replace('/^(.+?)(?:&emsp;)*$/', '${1}', $formStr);
  }

  /**
   * 像一颗树一样，使表单列表Select元素有层次感
   * array(array(id=>xx,pid=>xx,title=>xx, optAttr),...).
   * optAttr 的用例 如有子节点的禁用(即给有子节点的记录加 optAttr=>'disabled')
   */
  public function selectTree(&$data, $root, $sid, $parent = array(), $end = false)
  {
    $subSet = array();
    $decorate = null;
    $retStr = null;
    $current = null;
    foreach ($data as &$item) {
      if ($item['id'] == $root) {
        $current = $item;
      }
      if ($item['pid'] == $root) {
        $subSet[] = $item;
      }
    }
    $subSetSize = count($subSet);
    if ($current != null) {
      for ($i = 0; $i < count($parent) - 1; ++$i) {
        if ($parent[$i] != null) {
          $decorate .= '│&emsp;';
        } else {
          $decorate .= '&emsp;&emsp;';
        }
      }
      if (count($parent) > 0) {
        $decorate .= $end ? '└─' : '├─';
      }
      $selected = $current['id'] == $sid ? 'selected' : null;
      $cur_id = htmlspecialchars($current['id']);
      $cur_title = htmlspecialchars($current['title']);
      $retStr .= "<option {$selected} {$current['optAttr']} value=\"{$cur_id}\">{$decorate}{$cur_title}</option>" . CRLF;
      array_push($parent, 1);
      for ($i = 0; $i < $subSetSize; ++$i) {
        $end = ($i == $subSetSize - 1) ? true : false;
        if (true == $end) {
          $parent[count($parent) - 1] = 0;
        }
        $retStr .= $this->{__FUNCTION__}($data, $subSet[$i]['id'], $sid, $parent, $end);
      }
    } else {
      for ($i = 0; $i < $subSetSize; ++$i) {
        $retStr .= $this->{__FUNCTION__}($data, $subSet[$i]['id'], $sid);
      }
    }

    return $retStr;
  }

  /**
   * 像一颗树一样，使分类列表项有层次感，不支持分页
   * 输入：array(array('id'=>xx,'pid'=>xx,'title'=>xx),...).
   * 输出：array(array('id'=>xx,'pid'=>xx,'title'=>xx,'decorate'=>xx,'childCount'=>xx,'treeDepth'=>xx),...).
   */
  public function catTree(&$data, $root, $parent = array(), $end = false)
  {
    $subSet = array();
    $decorate = null;
    $ret = array();
    $current = null;
    foreach ($data as &$item) {
      if ($item['id'] == $root) {
        $current = $item;
      }
      if ($item['pid'] == $root) {
        $subSet[] = $item;
      }
    }
    $subSetSize = count($subSet);
    if ($current != null) {
      for ($i = 0; $i < count($parent) - 1; ++$i) {
        if ($parent[$i] != null) {
          $decorate .= '│&emsp;';
        } else {
          $decorate .= '&emsp;&emsp;';
        }
      }
      if (count($parent) > 0) {
        $decorate .= $end ? '└─' : '├─';
      }
      $current['decorate'] = $decorate;
      $current['childCount'] = $subSetSize;
      $current['treeDepth'] = count($parent);
      $ret[] = array_merge($ret, $current);

      array_push($parent, 1);
      for ($i = 0; $i < $subSetSize; ++$i) {
        $end = ($i == $subSetSize - 1) ? true : false;
        if (true == $end) {
          $parent[count($parent) - 1] = 0;
        }
        $tmp = $this->{__FUNCTION__}($data, $subSet[$i]['id'], $parent, $end);
        $ret = array_merge($ret, $tmp);
      }
    } else {
      for ($i = 0; $i < $subSetSize; ++$i) {
        $tmp = $this->{__FUNCTION__}($data, $subSet[$i]['id']);
        $ret = array_merge($ret, $tmp);
      }
    }

    return $ret;
  }

  /**
   * 调用程序在后台执行,请注意系统环境变量.
   *
   * @param string $cmd cmd命令
   *
   * @return bool 是否成功
   */
  public function execCmd($cmd)
  {
    if (substr(php_uname(), 0, 7) == 'Windows') {

      return pclose(popen('start /B '.$cmd, 'r'));
    }

    return exec($cmd.' > /dev/null &');
  }

}